"""BLE Batch Configuration Service.

Provides GATT characteristics for batch device configuration operations,
including job control, device selection, progress monitoring, and result reporting.
"""

import asyncio
import json
import logging
import struct
import time
from typing import Dict, List, Optional, Callable, Any
from enum import Enum

from ...config.batch_manager import (
    BatchConfigManager, BatchConfigJob, DeviceFilter, DeviceInfo,
    JobStatus, DeviceConfigStatus, ProgressInfo
)

logger = logging.getLogger(__name__)


# Service and Characteristic UUIDs
BATCH_CONFIG_SERVICE_UUID = "0000FF50-0000-1000-8000-00805F9B34FB"
JOB_CONTROL_UUID = "0000FF51-0000-1000-8000-00805F9B34FB"      # Write
JOB_STATUS_UUID = "0000FF52-0000-1000-8000-00805F9B34FB"       # Read, Notify
DEVICE_LIST_UUID = "0000FF53-0000-1000-8000-00805F9B34FB"      # Read, Write
CONFIG_RESULT_UUID = "0000FF54-0000-1000-8000-00805F9B34FB"    # Read, Notify


class BatchCommand(Enum):
    """Batch configuration control commands."""
    START_SCAN = 0x01           # Start device scan
    START_CONFIG = 0x02         # Start batch configuration
    PAUSE_JOB = 0x03           # Pause current job
    RESUME_JOB = 0x04          # Resume paused job
    CANCEL_JOB = 0x05          # Cancel current job
    SET_FILTERS = 0x06         # Set device filters
    SELECT_DEVICES = 0x07      # Select devices for configuration
    GET_PROGRESS = 0x08        # Get job progress


class DeviceListOperation(Enum):
    """Device list operations."""
    GET_DISCOVERED = 0x01      # Get discovered devices
    SET_SELECTED = 0x02        # Set selected devices
    GET_RESULTS = 0x03         # Get configuration results
    CLEAR_LIST = 0x04          # Clear device list


class BatchConfigService:
    """BLE Batch Configuration Service implementation."""
    
    def __init__(self, batch_manager: BatchConfigManager):
        """Initialize batch configuration service.
        
        Args:
            batch_manager: Batch configuration manager instance
        """
        self.batch_manager = batch_manager
        
        # Service state
        self.current_job_id: Optional[str] = None
        self.discovered_devices: List[DeviceInfo] = []
        self.selected_device_ids: List[str] = []
        self.scan_active = False
        
        # Notification callback
        self.notify_callback: Optional[Callable[[str, bytes], None]] = None
        
        # Service registration data
        self.service_data = self._create_service_definition()
        
        # Status notification interval
        self.status_notify_interval = 5  # 5 seconds
        self.status_task: Optional[asyncio.Task] = None
        
        # Progress tracking
        self.batch_manager.add_progress_callback(self._on_progress_update)
        
        logger.info("BatchConfigService initialized")
    
    def _create_service_definition(self) -> Dict[str, Any]:
        """Create GATT service definition.
        
        Returns:
            Service definition dictionary
        """
        return {
            "uuid": BATCH_CONFIG_SERVICE_UUID,
            "primary": True,
            "characteristics": [
                {
                    "uuid": JOB_CONTROL_UUID,
                    "flags": ["write"],
                    "value": bytearray(256),  # Command data
                    "descriptors": []
                },
                {
                    "uuid": JOB_STATUS_UUID,
                    "flags": ["read", "notify"],
                    "value": bytearray(128),  # Status information
                    "descriptors": [
                        {
                            "uuid": "00002902-0000-1000-8000-00805F9B34FB",  # CCC
                            "flags": ["read", "write"],
                            "value": bytearray([0x00, 0x00])
                        }
                    ]
                },
                {
                    "uuid": DEVICE_LIST_UUID,
                    "flags": ["read", "write"],
                    "value": bytearray(2048), # Device list data
                    "descriptors": []
                },
                {
                    "uuid": CONFIG_RESULT_UUID,
                    "flags": ["read", "notify"],
                    "value": bytearray(512),  # Configuration results
                    "descriptors": [
                        {
                            "uuid": "00002902-0000-1000-8000-00805F9B34FB",  # CCC
                            "flags": ["read", "write"],
                            "value": bytearray([0x00, 0x00])
                        }
                    ]
                }
            ]
        }
    
    async def start(self):
        """Start batch configuration service."""
        # Start periodic status notifications
        self.status_task = asyncio.create_task(self._status_notification_loop())
        
        logger.info("BatchConfigService started")
    
    async def stop(self):
        """Stop batch configuration service."""
        if self.status_task:
            self.status_task.cancel()
            try:
                await self.status_task
            except asyncio.CancelledError:
                pass
        
        # Cancel any active job
        if self.current_job_id:
            await self.batch_manager.job_controller.cancel_job(
                self.current_job_id, "Service stopped"
            )
        
        logger.info("BatchConfigService stopped")
    
    # GATT Characteristic Handlers
    
    async def handle_write_job_control(self, value: bytes) -> bool:
        """Handle write to JOB_CONTROL characteristic.
        
        Args:
            value: Command data bytes
            
        Returns:
            True if handled successfully
        """
        try:
            if not value:
                return False
            
            command = BatchCommand(value[0])
            params = value[1:] if len(value) > 1 else b''
            
            if command == BatchCommand.START_SCAN:
                return await self._handle_start_scan(params)
            
            elif command == BatchCommand.START_CONFIG:
                return await self._handle_start_config(params)
            
            elif command == BatchCommand.PAUSE_JOB:
                return await self._handle_pause_job(params)
            
            elif command == BatchCommand.RESUME_JOB:
                return await self._handle_resume_job(params)
            
            elif command == BatchCommand.CANCEL_JOB:
                return await self._handle_cancel_job(params)
            
            elif command == BatchCommand.SET_FILTERS:
                return await self._handle_set_filters(params)
            
            elif command == BatchCommand.SELECT_DEVICES:
                return await self._handle_select_devices(params)
            
            elif command == BatchCommand.GET_PROGRESS:
                return await self._handle_get_progress(params)
            
            else:
                logger.warning(f"Unknown batch command: {command}")
                return False
                
        except Exception as e:
            logger.error(f"Error handling job control: {e}")
            return False
    
    async def handle_read_job_status(self) -> bytes:
        """Handle read from JOB_STATUS characteristic.
        
        Returns:
            Status data bytes
        """
        try:
            if not self.current_job_id:
                # No active job
                return self._format_idle_status()
            
            # Get current job progress
            progress = await self.batch_manager.job_controller.get_job_status(
                self.current_job_id
            )
            
            if not progress:
                return self._format_idle_status()
            
            return self._format_job_status(progress)
            
        except Exception as e:
            logger.error(f"Error reading job status: {e}")
            return b'\\x00' * 32  # Return zeros on error
    
    async def handle_read_device_list(self) -> bytes:
        """Handle read from DEVICE_LIST characteristic.
        
        Returns:
            Device list data
        """
        try:
            # Return discovered devices in JSON format
            device_list = []
            for device in self.discovered_devices:
                device_data = {
                    "device_id": device.device_id,
                    "name": device.name,
                    "mac_address": device.mac_address,
                    "signal_strength": device.signal_strength,
                    "device_type": device.device_type,
                    "firmware_version": device.firmware_version,
                    "is_selected": device.device_id in self.selected_device_ids,
                    "is_configured": device.is_configured,
                    "is_online": device.is_online
                }
                device_list.append(device_data)
            
            response_data = {
                "operation": "discovered_devices",
                "count": len(device_list),
                "devices": device_list[:50]  # Limit to 50 devices
            }
            
            return json.dumps(response_data).encode('utf-8')[:2048]
            
        except Exception as e:
            logger.error(f"Error reading device list: {e}")
            return b'{\"error\": \"Failed to read device list\"}'
    
    async def handle_write_device_list(self, value: bytes) -> bool:
        """Handle write to DEVICE_LIST characteristic.
        
        Args:
            value: Device list operation data
            
        Returns:
            True if handled successfully
        """
        try:
            if not value:
                return False
            
            operation = DeviceListOperation(value[0])
            params = value[1:]
            
            if operation == DeviceListOperation.SET_SELECTED:
                return await self._handle_set_selected_devices(params)
            
            elif operation == DeviceListOperation.CLEAR_LIST:
                return await self._handle_clear_device_list()
            
            elif operation == DeviceListOperation.GET_RESULTS:
                return await self._handle_get_config_results(params)
            
            else:
                logger.warning(f"Unknown device list operation: {operation}")
                return False
                
        except Exception as e:
            logger.error(f"Error handling device list: {e}")
            return False
    
    async def handle_read_config_result(self) -> bytes:
        """Handle read from CONFIG_RESULT characteristic.
        
        Returns:
            Configuration result data
        """
        try:
            if not self.current_job_id:
                return b'{\"error\": \"No active job\"}'
            
            # Get job status with results
            progress = await self.batch_manager.job_controller.get_job_status(
                self.current_job_id
            )
            
            if not progress:
                return b'{\"error\": \"Job not found\"}'
            
            result_data = {
                "job_id": self.current_job_id,
                "total_devices": progress.devices_total,
                "completed_devices": progress.devices_completed,
                "failed_devices": progress.devices_failed,
                "success_rate": (progress.devices_completed / progress.devices_total * 100) 
                               if progress.devices_total > 0 else 0,
                "average_time": progress.average_device_time,
                "error_rate": progress.error_rate
            }
            
            return json.dumps(result_data).encode('utf-8')[:512]
            
        except Exception as e:
            logger.error(f"Error reading config result: {e}")
            return b'{\"error\": \"Failed to read results\"}'
    
    # Command Handlers
    
    async def _handle_start_scan(self, params: bytes) -> bool:
        """Handle start device scan command."""
        try:
            # Parse scan parameters
            scan_params = {}
            if params:
                scan_params = json.loads(params.decode('utf-8'))
            
            duration = scan_params.get('duration', 30)
            device_filter = self._parse_device_filter(scan_params.get('filter', {}))
            
            # Start scanning
            self.scan_active = True
            self.discovered_devices = await self.batch_manager.device_discovery.start_batch_scan(
                duration=duration,
                filters=[device_filter] if device_filter else None
            )
            self.scan_active = False
            
            # Send scan completion notification
            await self._send_scan_completion_notification()
            
            logger.info(f"Device scan completed, found {len(self.discovered_devices)} devices")
            return True
            
        except Exception as e:
            logger.error(f"Start scan error: {e}")
            self.scan_active = False
            return False
    
    async def _handle_start_config(self, params: bytes) -> bool:
        """Handle start batch configuration command."""
        try:
            # Parse configuration parameters
            if not params:
                return False
            
            config_params = json.loads(params.decode('utf-8'))
            
            job_name = config_params.get('name', f'Batch Config {int(time.time())}')
            config_data = config_params.get('config_data', {})
            description = config_params.get('description', '')
            
            # Create device filter for selected devices
            device_filter = DeviceFilter()
            if self.selected_device_ids:
                # Filter to only selected devices
                target_devices = [d for d in self.discovered_devices 
                                if d.device_id in self.selected_device_ids]
            else:
                # Use all discovered devices
                target_devices = self.discovered_devices
            
            if not target_devices:
                logger.warning("No devices selected for configuration")
                return False
            
            # Create batch job
            job_id = await self.batch_manager.create_batch_job(
                name=job_name,
                config_data=config_data,
                device_filter=device_filter,
                description=description
            )
            
            # Start job execution
            success = await self.batch_manager.execute_batch_job(job_id)
            
            if success:
                self.current_job_id = job_id
                logger.info(f"Started batch configuration job: {job_id}")
                return True
            else:
                logger.error(f"Failed to start batch configuration job: {job_id}")
                return False
                
        except Exception as e:
            logger.error(f"Start config error: {e}")
            return False
    
    async def _handle_pause_job(self, params: bytes) -> bool:
        """Handle pause job command."""
        try:
            if not self.current_job_id:
                return False
            
            success = await self.batch_manager.job_controller.pause_job(
                self.current_job_id
            )
            
            if success:
                await self._send_job_status_notification()
            
            return success
            
        except Exception as e:
            logger.error(f"Pause job error: {e}")
            return False
    
    async def _handle_resume_job(self, params: bytes) -> bool:
        """Handle resume job command."""
        try:
            if not self.current_job_id:
                return False
            
            success = await self.batch_manager.job_controller.resume_job(
                self.current_job_id
            )
            
            if success:
                await self._send_job_status_notification()
            
            return success
            
        except Exception as e:
            logger.error(f"Resume job error: {e}")
            return False
    
    async def _handle_cancel_job(self, params: bytes) -> bool:
        """Handle cancel job command."""
        try:
            if not self.current_job_id:
                return False
            
            reason = "User cancelled"
            if params:
                cancel_params = json.loads(params.decode('utf-8'))
                reason = cancel_params.get('reason', reason)
            
            success = await self.batch_manager.job_controller.cancel_job(
                self.current_job_id, reason
            )
            
            if success:
                self.current_job_id = None
                await self._send_job_status_notification()
            
            return success
            
        except Exception as e:
            logger.error(f"Cancel job error: {e}")
            return False
    
    async def _handle_set_filters(self, params: bytes) -> bool:
        """Handle set device filters command."""
        try:
            if not params:
                return False
            
            filter_params = json.loads(params.decode('utf-8'))
            device_filter = self._parse_device_filter(filter_params)
            
            if device_filter:
                # Apply filters to discovered devices
                filtered_devices = self.batch_manager.device_discovery._apply_single_filter(
                    self.discovered_devices, device_filter
                )
                self.discovered_devices = filtered_devices
                
                logger.info(f"Applied filters, {len(self.discovered_devices)} devices remain")
                return True
            
            return False
            
        except Exception as e:
            logger.error(f"Set filters error: {e}")
            return False
    
    async def _handle_select_devices(self, params: bytes) -> bool:
        """Handle select devices command."""
        try:
            if not params:
                return False
            
            select_params = json.loads(params.decode('utf-8'))
            action = select_params.get('action', 'set')  # set, add, remove
            device_ids = select_params.get('device_ids', [])
            
            if action == 'set':
                self.selected_device_ids = device_ids
            elif action == 'add':
                self.selected_device_ids.extend(device_ids)
                self.selected_device_ids = list(set(self.selected_device_ids))
            elif action == 'remove':
                self.selected_device_ids = [
                    d for d in self.selected_device_ids if d not in device_ids
                ]
            
            logger.info(f"Selected {len(self.selected_device_ids)} devices")
            return True
            
        except Exception as e:
            logger.error(f"Select devices error: {e}")
            return False
    
    async def _handle_get_progress(self, params: bytes) -> bool:
        """Handle get progress command."""
        try:
            await self._send_job_status_notification()
            return True
            
        except Exception as e:
            logger.error(f"Get progress error: {e}")
            return False
    
    async def _handle_set_selected_devices(self, params: bytes) -> bool:
        """Handle set selected devices operation."""
        try:
            if not params:
                return False
            
            device_data = json.loads(params.decode('utf-8'))
            selected_ids = device_data.get('selected_device_ids', [])
            
            # Validate device IDs
            valid_ids = [device.device_id for device in self.discovered_devices]
            self.selected_device_ids = [
                device_id for device_id in selected_ids if device_id in valid_ids
            ]
            
            logger.info(f"Set {len(self.selected_device_ids)} selected devices")
            return True
            
        except Exception as e:
            logger.error(f"Set selected devices error: {e}")
            return False
    
    async def _handle_clear_device_list(self) -> bool:
        """Handle clear device list operation."""
        try:
            self.discovered_devices.clear()
            self.selected_device_ids.clear()
            
            logger.info("Cleared device list")
            return True
            
        except Exception as e:
            logger.error(f"Clear device list error: {e}")
            return False
    
    async def _handle_get_config_results(self, params: bytes) -> bool:
        """Handle get configuration results operation."""
        try:
            await self._send_config_result_notification()
            return True
            
        except Exception as e:
            logger.error(f"Get config results error: {e}")
            return False
    
    # Helper Methods
    
    def _parse_device_filter(self, filter_data: Dict[str, Any]) -> Optional[DeviceFilter]:
        """Parse device filter from JSON data."""
        try:
            return DeviceFilter(
                device_types=filter_data.get('device_types'),
                firmware_versions=filter_data.get('firmware_versions'),
                signal_strength=filter_data.get('signal_strength'),
                location_tags=filter_data.get('location_tags'),
                last_seen_hours=filter_data.get('last_seen_hours'),
                exclude_configured=filter_data.get('exclude_configured', True),
                exclude_offline=filter_data.get('exclude_offline', True)
            )
        except Exception as e:
            logger.error(f"Error parsing device filter: {e}")
            return None
    
    def _format_idle_status(self) -> bytes:
        """Format idle status data."""
        return struct.pack(
            '>32s BBBBB HHH f',
            b'idle',                    # Job ID (32 bytes)
            JobStatus.PENDING.value,    # Status (1 byte)  
            0,                         # Progress (1 byte)
            0,                         # Current phase (1 byte)
            0,                         # Reserved (1 byte)
            0,                         # Reserved (1 byte)
            0,                         # Total devices (2 bytes)
            0,                         # Completed devices (2 bytes)
            0,                         # Failed devices (2 bytes)
            0.0                        # Throughput (4 bytes)
        )
    
    def _format_job_status(self, progress: ProgressInfo) -> bytes:
        """Format job status data."""
        job_id_bytes = progress.job_id.encode('utf-8')[:32].ljust(32, b'\\x00')
        phase_bytes = progress.current_phase.encode('utf-8')[:16].ljust(16, b'\\x00')
        
        return struct.pack(
            '>32s B B 16s H H H f f f',
            job_id_bytes,                        # Job ID (32 bytes)
            progress.status.value,               # Status (1 byte)
            int(progress.progress_percentage),   # Progress % (1 byte)
            phase_bytes,                         # Current phase (16 bytes)
            progress.devices_total,              # Total devices (2 bytes)
            progress.devices_completed,          # Completed devices (2 bytes)
            progress.devices_failed,             # Failed devices (2 bytes)
            progress.current_throughput,         # Throughput (4 bytes)
            progress.average_device_time,        # Average time (4 bytes)
            progress.error_rate                  # Error rate (4 bytes)
        )
    
    # Notification Methods
    
    async def _send_scan_completion_notification(self):
        """Send scan completion notification."""
        try:
            notification_data = {
                "event": "scan_completed",
                "device_count": len(self.discovered_devices),
                "timestamp": int(time.time())
            }
            
            data = json.dumps(notification_data).encode('utf-8')[:512]
            await self._send_notification(CONFIG_RESULT_UUID, data)
            
        except Exception as e:
            logger.error(f"Error sending scan notification: {e}")
    
    async def _send_job_status_notification(self):
        """Send job status notification."""
        try:
            status_data = await self.handle_read_job_status()
            await self._send_notification(JOB_STATUS_UUID, status_data)
            
        except Exception as e:
            logger.error(f"Error sending status notification: {e}")
    
    async def _send_config_result_notification(self):
        """Send configuration result notification."""
        try:
            result_data = await self.handle_read_config_result()
            await self._send_notification(CONFIG_RESULT_UUID, result_data)
            
        except Exception as e:
            logger.error(f"Error sending result notification: {e}")
    
    async def _send_notification(self, characteristic_uuid: str, data: bytes):
        """Send notification for a characteristic.
        
        Args:
            characteristic_uuid: UUID of characteristic
            data: Notification data
        """
        if self.notify_callback:
            self.notify_callback(characteristic_uuid, data)
    
    # Event Handlers
    
    def _on_progress_update(self, progress: ProgressInfo):
        """Handle progress update from batch manager."""
        if progress.job_id == self.current_job_id:
            # Send progress notification
            asyncio.create_task(self._send_job_status_notification())
            
            # Check if job completed
            if progress.status in [JobStatus.COMPLETED, JobStatus.FAILED, JobStatus.CANCELLED]:
                self.current_job_id = None
    
    # Background Tasks
    
    async def _status_notification_loop(self):
        """Background status notification loop."""
        while True:
            try:
                await asyncio.sleep(self.status_notify_interval)
                
                # Send status update if job is active
                if self.current_job_id:
                    await self._send_job_status_notification()
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Status notification loop error: {e}")
    
    # Service Interface
    
    def set_notify_callback(self, callback: Callable[[str, bytes], None]):
        """Set notification callback.
        
        Args:
            callback: Callback function for notifications (uuid, data)
        """
        self.notify_callback = callback
    
    def get_service_definition(self) -> Dict[str, Any]:
        """Get GATT service definition.
        
        Returns:
            Service definition for registration
        """
        return self.service_data
    
    def get_batch_statistics(self) -> Dict[str, Any]:
        """Get batch configuration service statistics.
        
        Returns:
            Service statistics
        """
        return {
            "batch_config_service": {
                "is_running": self.status_task is not None and not self.status_task.done(),
                "current_job_id": self.current_job_id,
                "discovered_devices": len(self.discovered_devices),
                "selected_devices": len(self.selected_device_ids),
                "scan_active": self.scan_active,
                "notification_interval": self.status_notify_interval
            }
        }
    
    async def cleanup(self):
        """Clean up service resources."""
        await self.stop()
        
        # Remove progress callback
        self.batch_manager.remove_progress_callback(self._on_progress_update)
        
        # Clear state
        self.discovered_devices.clear()
        self.selected_device_ids.clear()
        self.current_job_id = None
        
        logger.info("BatchConfigService cleanup completed")


# Example usage and testing
if __name__ == "__main__":
    import asyncio
    from unittest.mock import MagicMock
    
    async def test_batch_config_service():
        """Test batch configuration service."""
        # Create mock batch manager
        batch_manager = MagicMock()
        
        # Create service
        service = BatchConfigService(batch_manager)
        
        # Mock notification callback
        notifications = []
        def notify_callback(uuid, data):
            notifications.append((uuid, data))
        
        service.set_notify_callback(notify_callback)
        
        # Start service
        await service.start()
        
        try:
            # Test service definition
            service_def = service.get_service_definition()
            print(f"Service UUID: {service_def['uuid']}")
            print(f"Characteristics: {len(service_def['characteristics'])}")
            
            # Test scan command
            scan_cmd = bytes([BatchCommand.START_SCAN.value]) + json.dumps({
                "duration": 10,
                "filter": {"device_types": ["printer"]}
            }).encode()
            
            success = await service.handle_write_job_control(scan_cmd)
            print(f"Scan command: {'success' if success else 'failed'}")
            
            # Test status reading
            status_data = await service.handle_read_job_status()
            print(f"Status data length: {len(status_data)} bytes")
            
            # Test device selection
            select_cmd = bytes([BatchCommand.SELECT_DEVICES.value]) + json.dumps({
                "action": "set",
                "device_ids": ["device_001", "device_002"]
            }).encode()
            
            success = await service.handle_write_job_control(select_cmd)
            print(f"Device selection: {'success' if success else 'failed'}")
            
            # Wait for notifications
            await asyncio.sleep(2)
            print(f"Received {len(notifications)} notifications")
            
            # Get statistics
            stats = service.get_batch_statistics()
            print(f"Service statistics:")
            for key, value in stats["batch_config_service"].items():
                print(f"  {key}: {value}")
                
        finally:
            await service.cleanup()
    
    # Run test
    asyncio.run(test_batch_config_service())